<?php

namespace Module\Admin;

use W3\Json;
use Manager\Options as Manager;
use W3\Router;
use W3\Html;
use W3\Client;
use W3\Exception;

!defined('W3_ROOT_DIR') AND exit;

/**
 * 基本设置组件
 *
 * @author edikud
 * @date 2022/10/22
 * @copyright Copyright (c) 2022 W3 (http://www.mcooo.com)
 * @license GNU General Public License 2.0
 */
class Permalink extends Manager
{
    /**
     * 初始化
     *
     * @access protected
     * @return void
     */
    protected function init()
    {
		# 必须为管理员以上权限
		$this->auth->check('admin');

		# 定义变量默认数据
        $this->parameter([

			# 设置主体
			'main' => 'admin.permalink',
			
			'routeMap' => [
			    'index', 'login', 'logout', 'register', 
			    'upload', 'admin', 'tag', 'author',
				'search', 'post', 'page', 'category',
				'comment_page', 'error404', 'feedback'
			],
			
		    'encodeIDS' => ['{cid}',     '{alias}',     '{category}',     '{uid}',     '{mid}',     '{name}',     '{directory}'],
			
		    'decodeIDS' => ['{cid:\d+}', '{alias:[^/]+}', '{category:\d+}', '{uid:\d+}', '{mid:\d+}', '{name:\w*}', '{directory:.*}']
			
		], true);
	}
	
    /**
     * 数据库中的路由数据
     *
     * @access private
     * @var array
     */
	 
    private $_routingTable;
	
    private $_rules;	

    public function execute()
    {
		$this->_rules = Router::export();

        foreach ($this->parameter->routeMap as $route) 
		{
			if(isset($this->_rules[$route])){
			    $this->_routingTable[$route] = $this->decodeRule($this->_rules[$route]->url);
			}
		}
    }

    /**
     * 绑定动作
     *
     * @access public
     * @return void
     */
    public function action()
    {
		$this->on(!$this->request->action || $this->request->is('action=index'))->index();
		$this->on($this->request->isPost() && (!$this->request->action || $this->request->is('action=index')))->update();
		$this->on($this->request->is('action=reset'))->_reset();
    }

    public function index()
    {
		/** 输出内容 */
		$this->layout->set($this->form(), 'body');
		
		$this->view();
    }

    public function _reset()
    {
		$modified = false;
		$update = $this->config->routes;
        foreach ($this->parameter->routeMap as $route) 
		{
			if(isset($update[$route])){
			    unset($update[$route]);
				$modified = true;
			}
		}

        $modified && $this->updateOptions('routes', Json::encode($update));

        $this->notice(__("路由已经恢复默认"));
		
		$this->goBack();
    }

    /**
     * 检验规则是否冲突
     *
     * @access public
     * @param string $value 路由规则
     * @return boolean
     */
    public function checkRule($value, $key)
    {
		$replace = [
		    'cid'=>'123',
			'alias'=>'alias',
			'category'=>'123',
			'uid'=>'123',
			'comment_page'=>'123',
			'page'=>'123',
			'mid'=>'123',
		];
		
		$url = '';
		$arr = explode('{', $value);
        foreach ($arr as $rule) 
		{
		    if(strpos($rule, '}')){
				
				$arr = explode('}', $rule);
				$rule = $arr[0];
				//strpos($rule, '?') && $rule = strstr($rule, '?', true);
				strpos($rule, ':') && $rule = strstr($rule, ':', true);

		        $url .=  (isset($replace[$rule]) ? $replace[$rule] : $rule) . $arr[1];
				
		    } else {
				$url .= $rule;
			}
		}

		$arr = strpos($url, ' ') ? explode(' ', $url) : ['GET', $url];
		if(count($arr) > 1){
			
			list($method, $url) = $arr;
		    if(in_array($method, Router::allowedMethods())){

				$rule = Router::match($method, $url);
			}
		}
		
		$index = $rule ? $rule->name : '';
		
		# 允许文章路由规则和独立页面路由规则相同
		return !$index || $index && ($key == $index || 2 == count(array_intersect(['post', 'page'], [$key, $index])));
		
		# 不允许文章路由规则和独立页面路由规则相同
		//return !$index || $index && ($key == $index);
    }
	
    /**
     * 修改路由
     */
    public function update()
    {
		# 设置路由路径前缀
		Router::prefix($this->request->rewrite ? $this->config->rootUrl : rtrim($this->config->rootUrl, '/') . '/index.php');	
		
		$form = $this->form();
		
		/** 验证格式 */
        if ($form->validate()) {
            $this->goBack();
        }

        $settings = $this->request->from('rewrite');

        $modified = false;
		$routings = $this->request->from(array_keys($_POST));
		
		$update = $this->config->routes;
        foreach ($this->_routingTable as $key => $value) 
		{
            if (isset($routings[$key]) && $routings[$key] != $this->decodeRule($this->_rules[$key]->url)) {
				
				$routings[$key] = $this->encodeRule($routings[$key]);
                $update[$key] = $routings[$key];
				$modified = true;
            }
        }

		if ($modified) {
			$settings['routes'] = Json::encode($update);
		}

        foreach ($settings as $name => $value) 
		{
            $this->updateOptions($name, $value);
			$modified = true;
        }

        if ($modified) {

            $this->notice(__("路由变更已经保存"));

        } else {
            $this->notice(__("路由未变更"));
        }
		
		$this->redirect($this->adminUrl('permalink', false));
    }
	
    /**
     * 检测是否可以rewrite
     *
     * @param string $value 是否打开rewrite
     * @return bool
     */
    public function checkRewrite(string $value)
    {
        if ($value) {
			
		    $index = !$this->config->rewrite ? '' : rtrim($this->config->rootUrl, '/') . '/index.php';
		    $testUrl = $this->request->domain() . $index . Router::ruleUrl('login');

            /** 首先直接请求远程地址验证 */
            $client = Client::get();
            $hasWrote = false;
            $parsed = parse_url($this->config->siteUrl);
            $basePath = empty($parsed['path']) ? '/' : $parsed['path'];
            $basePath = rtrim($basePath, '/') . '/';

            if (!file_exists(W3_ROOT_DIR . '.htaccess') && strpos(php_sapi_name(), 'apache') !== false) {
                if (is_writeable(W3_ROOT_DIR)) {
                    $hasWrote = file_put_contents(W3_ROOT_DIR . '.htaccess', 
"<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase {$basePath}
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ {$basePath}index.php [L]
</IfModule>");
                }
            }

            try {
                if ($client) {
                    /** 发送一个rewrite地址请求 */
                    $client
                        ->setHeader('X-Requested-With', 'XMLHttpRequest')
                        ->send($testUrl);

                    if (200 == $client->getResponseStatus()) {
                        return true;
                    }
                }

                if (false == $hasWrote) {
                    @unlink(W3_ROOT_DIR . '.htaccess');

                    //增强兼容性,使用wordpress的redirect式rewrite规则,虽然效率有点地下,但是对fastcgi模式兼容性较好
                    $hasWrote = file_put_contents(W3_ROOT_DIR . '.htaccess', 
"<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase {$basePath}
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ {$basePath}index.php [L]
</IfModule>");

                    //再次进行验证
                    $client = Client::get();

                    if ($client) {
                        /** 发送一个rewrite地址请求 */
                        $client
                            ->setHeader('X-Requested-With', 'XMLHttpRequest')
                            ->send($testUrl);

                        if (200 == $client->getResponseStatus()) {
                            return true;
                        }
                    }

                    unlink(W3_ROOT_DIR . '.htaccess');
                }
            } catch (Exception $e) {
                if (false != $hasWrote) {
                    @unlink(W3_ROOT_DIR . '.htaccess');
                }
                return false;
            }

            return false;
        } elseif(file_exists(W3_ROOT_DIR . '.htaccess')) {
            @unlink(W3_ROOT_DIR . '.htaccess');
        }

        return true;
    }
	
    /**
     * 输出表单结构
     *
     * @access public
     * @return Form
     */
    public function form()
    {
        /** 构建表格 */
        $form = Html::form()->addClass('rewrite-form');

        /** 是否使用地址重写功能 */
        $rewrite = Html::radio('rewrite', ['0' => __('不启用'), '1' => __('启用')], $this->config->rewrite)
		    ->label(__('是否使用地址重写功能'))
		    ->description(__('地址重写即 rewrite 功能是某些服务器软件提供的优化内部连接的功能.') . '<br />' . __('打开此功能可以让你的链接看上去完全是静态地址.'));
			
        $errorStr = __('重写功能检测失败, 请检查你的服务器设置') . '<br />' . __('调整你的目录权限, 或者手动创建一个.htaccess文件.');
		($this->request->rewrite != $this->config->rewrite) && $rewrite->addRule([$this, 'checkRewrite'], $errorStr);	
        $form->addInput($rewrite);

        /** 自定义文章路径 */
        $post = Html::text('post', $this->_routingTable['post'])
		    ->label(__('自定义文章路径'))
		    ->description(__('可用参数: <code>{cid}</code> 文章 ID, <code>{alias}</code> 文章缩略名, <code>{category}</code> 分类, <code>{directory}</code> 多级分类.')
            . '<br />' . __('请在路径中至少包含上述 {cid} {alias} 中的一项参数.') . '<br />' .  __('注意：{directory} 匹配斜线组合项, 加前缀确保链接不冲突比如：post/{directory}'));
			
        $form->addInput($post);
		
        /** 自定义独立页面路径 */
        $page = Html::text('page', $this->_routingTable['page'])
		    ->label(__('独立页面路径'))
		    ->description(__('可用参数: <code>{cid}</code> 页面 ID, <code>{alias}</code> 页面缩略名.')
            . '<br />' . __('请在路径中至少包含上述 {cid} {alias} 中的一项参数. '));
			
        $form->addInput($page);

        /** 分类页面 */
        $category = Html::text('category', $this->_routingTable['category'])
		    ->label(__('分类路径'))
		    ->description(__('可用参数: <code>{mid}</code> 分类 ID, <code>{alias}</code> 分类缩略名, <code>{directory}</code> 多级分类.')
            . '<br />' . __('请在路径中至少包含上述 {mid} {alias} {directory} 中的一项参数. ') . '<br />' .  __('注意：{directory} 匹配斜线组合项, 加前缀确保链接不冲突比如：category/{directory}'));
			
        $form->addInput($category);
		
        /** 标签页面 */
        $tag = Html::text('tag', $this->_routingTable['tag'])
		    ->label(__('标签路径'))
		    ->description(__('可用参数: <code>{mid}</code> 标签 ID, <code>{alias}</code> 标签缩略名.')
            . '<br />' . __('请在路径中至少包含上述 {mid} {alias} 中的一项参数.'));
			
        $form->addInput($tag);		
		
        /** 用户页面 */
        $author = Html::text('author', $this->_routingTable['author'])
		    ->label(__('用户路径'))
		    ->description(__('可用参数: <code>{uid}</code> 用户 ID, <code>{name}</code> 用户名.')
            . '<br />' . __('请在路径中至少包含上述 {uid} {name} 中的一项参数. '));
			
        $form->addInput($author);


        $button = Html::a($this->adminUrl('permalink/reset', false), __('恢复默认设置'))->addClass('btn btn-outline-primary w3-permalink-reset mr-2');
        $form->set($button);
		
        /** 提交按钮 */
        $submit = Html::submit(__('保存设置'));
		
        $form->set($submit);

		$post->addRule(
		    [$this, 'checkRequire'],
			__('自定义文章路径中没有包含 {cid} 或者 {alias} '),
			['{cid}', '{alias}']
		);

		$page->addRule(
		    [$this, 'checkRequire'],
			__('独立页面路径中没有包含 {cid} 或者 {alias} '),
			['{cid}', '{alias}']
		);
		
		$category->addRule(
		    [$this, 'checkRequire'],
			__('分类路径中没有包含 {mid} 或者 {alias} 或者 {directory}'),
			['{mid}', '{alias}', '{directory}']
		);
		
		$tag->addRule(
		    [$this, 'checkRequire'],
			__('标签路径中没有包含 {mid} 或者 {alias} '),
			['{mid}', '{alias}']
		);
		
		$author->addRule(
		    [$this, 'checkRequire'],
			__('用户路径中没有包含 {uid} 或者 {name} '),
			['{uid}', '{name}']
		);
		
        $inputs = $form->getInputs();
        foreach ($inputs as $key => $val) 
		{
            if(isset($this->_routingTable[$key])){
				$form->getInput($key)->addRule([$this, 'checkRule'], __("链接与现有规则存在冲突! 它可能影响解析效率, 建议你重新分配一个规则."), $key);
				$form->getInput($key)->addRule([$this, 'checkRuleError'], __("链接规则错误.   链接要 '/' 开头且不含有 '}{'   "));
			}
        }
		
        return $form;
    }

    /**
     * 编码自定义的路径
     *
     * @access private
     * @param string $rule 待编码的路径
     * @return string
     */
    protected function encodeRule($rule)
    {
        return str_replace(
		    $this->parameter->encodeIDS,
            $this->parameter->decodeIDS, 
			$rule
		);
    }
	
    /**
     * 解析自定义的路径
     *
     * @access private
     * @param string $rule 待解码的路径
     * @return string
     */
    protected function decodeRule($rule)
    {
        return str_replace(
		    $this->parameter->decodeIDS, 
		    $this->parameter->encodeIDS,
			$rule
		);
    }

    /**
     * 检查是否含有硬性必要参数
     * 
     * @access public
     * @return void
     */
    public function checkRequireHard($str, array $requires)
    {
		$pass = true;
        foreach ($requires as $require) 
		{
			if(false !== strpos($require, '#')){
				list($a, $b) = explode('#', $require);
				
				if(false === strpos($str, $a) && false === strpos($str, $b)){
					$pass = false;
					break;
				}
			} elseif(false === strpos($str, $require)) {
				$pass = false;
				break;
			}
		}

		return $pass;
    }

    /**
     * 检查是否含有必要参数
     * 
     * @access public
     * @return void
     */
    public function checkRequire($str, array $params)
    {
		$list = [];
		$arr = explode('{', $str);
		
        foreach ($arr as $rule) 
		{
		    if(strpos($rule, '}')){
				
				$rule = strstr($rule, '}', true);

				strpos($rule, '?') && $rule = strstr($rule, '?', true);
				strpos($rule, ':') && $rule = strstr($rule, ':', true);
				$rule = '{' . $rule . '}';

		        $list[] = $rule;
		    }
		}

		return !empty(array_intersect($params, $list));
    }

    /**
     * 检查链接规则是否错误
     * 
     * @access public
     * @return void
     */
    public function checkRuleError($str)
    {
		return FALSE === strpos($str, '}{') && 0 === strpos($str, '/');
    }
	
}